package com.lw.chat.client;

import com.lw.chat.message.RpcRequestMessage;
import com.lw.chat.protocol.MessageCodecSharable;
import com.lw.chat.protocol.ProtocolFrameDecoder;
import com.lw.chat.protocol.SequenceIdGenerator;
import com.lw.chat.server.handler.RpcResponseMessageHandler;
import com.lw.chat.server.service.HelloService;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.concurrent.DefaultPromise;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class RpcClientManager {

    private static Channel channel = null;

    private static final Object lockObj = new Object();//锁对象，加锁用的

    public static void main(String[] args) {
//        getChannel();
//        RpcRequestMessage message = new RpcRequestMessage(
//                1,
//                "com.lw.chat.server.service.HelloService",  //调用的接口名
//                "sayHello", //调用的方法名
//                String.class, //方法返回值类型
//                new Class[]{String.class}, //方法参数类型
//                new Object[]{"张三"} //方法参数
//        );
//        channel.writeAndFlush(message);

        HelloService service = getProxyService(HelloService.class);
        String ret = service.sayHello("张三");
        System.out.println(ret);

    }

    /**
     * 创建代理类
     *
     * @param <T>
     * @return
     */
    public static <T> T getProxyService(Class<T> serviceClass) {
        ClassLoader loader = serviceClass.getClassLoader();//类加载器
        Class<?>[] interfaces = new Class[]{serviceClass};//要代理的接口

        Object o = Proxy.newProxyInstance(loader, interfaces, new InvocationHandler() {
            /**
             * @param proxy 代理对象
             * @param method 要代理的方法
             * @param args 方法参数
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                int sequenceId = SequenceIdGenerator.nextId();

                //1. 将方法调用转为消息对象
                RpcRequestMessage message = new RpcRequestMessage(
                        sequenceId,
                        serviceClass.getName(),
                        method.getName(),
                        method.getReturnType(),//方法返回值类型
                        method.getParameterTypes(),//方法参数类型
                        args
                );
                //2. 将消息对象发送出去
                getChannel().writeAndFlush(message);

                //3. 准备Promise对象，接收返回值, getChannel().eventLoop() 指定Promise异步接收结果的线程
                DefaultPromise<Object> promise = new DefaultPromise<>(getChannel().eventLoop());
                RpcResponseMessageHandler.promiseMap.put(sequenceId,promise);

                promise.addListener(future -> {
                   //此处的线程  getChannel().eventLoop()
                });

                //4. 等待Promise结果返回
                promise.await();//阻塞，等待异步调用的结果返回
                if(promise.isSuccess()){
                    return promise.getNow();
                }else{
                    Throwable cause = promise.cause();
                    throw new RuntimeException(cause);
                }
            }
        });
        return (T) o;
    }

    public static Channel getChannel() {
        if (channel != null) return channel;

        /*
            防止多线程的并发问题，双重检查
            假设有t1,t2两个线程，t1先加锁，此时还没有channel，所以t1调用initChannel()创建了channel，释放锁。
            此时t2进入代码块，如果没有if判断，则t2也调用一次initChannel()方法创建了channel，
            和此处代码目的不符合，目的是只调用一次initChannel()方法，就是单例模式
         */
        synchronized (lockObj) {
            if (channel != null) {
                return channel;
            }

            initChannel();
            return channel;
        }
    }

    /**
     * 初始化Channel
     */
    private static void initChannel() {
        NioEventLoopGroup group = new NioEventLoopGroup();
        LoggingHandler loggingHandler = new LoggingHandler();
        MessageCodecSharable messageCodecSharable = new MessageCodecSharable();

        RpcResponseMessageHandler rpcResponseMessageHandler = new RpcResponseMessageHandler();

        Bootstrap bootstrap = new Bootstrap();
        bootstrap.channel(NioSocketChannel.class);
        bootstrap.group(group);
        bootstrap.handler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ch.pipeline().addLast(new ProtocolFrameDecoder());
                ch.pipeline().addLast(loggingHandler);
                ch.pipeline().addLast(messageCodecSharable);
                ch.pipeline().addLast(rpcResponseMessageHandler);
            }
        });

        try {
            channel = bootstrap.connect("localhost", 8080).sync().channel();
            channel.closeFuture().addListener(future -> {
                group.shutdownGracefully();
            });
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
